其他
Python进阶——迭代器和可迭代对象有什么区别?
阅读本文大约需要 10 分钟。
容器
in
或 not in
来判断一个元素存在/不存在于一个容器内。print('a' not in 'xyz') # True
print(1 in [1, 2, 3]) # True
print(2 not in (1, 2, 3)) # False
print('x' not in {'a', 'b', 'c'}) # True
print('a' in {'a': 1, 'b': 2}) # True
str
、list
、tuple
、set
、dict
都可以通过 in
或 not in
来判断一个元素是否在存在/不存在这个实例中,所以这些类型我们都可以称作「容器」。in
或 not in
来判断呢?__contains__
方法。__contains__
方法就可以了:def __init__(self):
self.items = [1, 2]
def __contains__(self, item):
return item in self.items
a = A()
print(1 in a) # True
print(2 in a) # True
print(3 in a) # False
A
定义了 __contains__
方法,所以我们就可以使用 1 in a
的方式去判断这个元素是否在 A
这个容器内。__contains__
方法,那么它就是一个「容器」。in
判断元素是否在容器内之外,另外一个常用的功能是:输出容器内的所有元素。for x in [1, 2, 3]
,就可以迭代出容器内的所有元素。迭代器
for
的方式迭代出容器内的所有数据,这就需要这个类实现「迭代器协议」。__iter__
:这个方法返回对象本身,即self
__next__
:这个方法每次返回迭代的值,在没有可迭代元素时,抛出StopIteration
异常
class A:
"""A 实现了迭代器协议 它的实例就是一个迭代器"""
def __init__(self, n):
self.idx = 0
self.n = n
def __iter__(self):
print('__iter__')
return self
def __next__(self):
if self.idx < self.n:
val = self.idx
self.idx += 1
return val
else:
raise StopIteration()
# 迭代元素
a = A(3)
for i in a:
print(i)
# 再次迭代 没有元素输出 因为迭代器只能迭代一次
for i in a:
print(i)
# __iter__
# 0
# 1
# 2
# __iter__
A
,它内部实现了 __iter__
和 __next__
方法。__iter__
方法返回了 self
,__next__
方法实现了具体的迭代细节。a = A(3)
,在执行 for i in a
时,我们看到调用了 __iter__
方法,然后依次输出 __next__
中的元素。for
循环时,实际执行流程是这样的:for i in a
相当于执行iter(a)
每次迭代时会执行一次 __next__
方法,返回一个值如果没有可迭代的数据,抛出 StopIteration
异常,for
会停止迭代
for i in a
时,如果再次执行迭代,将不会有任何数据输出。for i in A(3):
print(i)
可迭代对象
__iter__
方法返回一个迭代器,那么这个对象就是「可迭代对象」。# A是迭代器 因为它实现了 __iter__ 和__next__方法
def __init__(self, n):
self.idx = 0
self.n = n
def __iter__(self):
return self
def __next__(self):
if self.idx < self.n:
val = self.idx
self.idx += 1
return val
else:
raise StopIteration()
class B:
# B不是迭代器 但B的实例是一个可迭代对象
# 因为它只实现了 __iter__
# __iter__返回了A的实例 迭代细节交给了A
def __init__(self, n):
self.n = n
def __iter__(self):
return A(self.n)
# a是一个迭代器 同时也是一个可迭代对象
a = A(3)
for i in a:
print(i)
# <__main__.A object at 0x10eb95550>
print(iter(a))
# b不是迭代器 但它是可迭代对象 因为它把迭代细节交给了A
b = B(3)
for i in b:
print(i)
# <__main__.A object at 0x10eb95450>
print(iter(b))
A
和 B
,A
实现了 __iter__
和 __next__
方法。B
只实现了 __iter__
,并没有实现 __next__
,而且它的 __iter__
返回值是一个 A
的实例。A
来说:A
是一个「迭代器」,因为其实现了迭代器协议__iter__
和__next__
同时 A
的__iter__
方法返回了实例本身self
,也就是说返回了一个迭代器,所以A
的实例a
也是一个「可迭代对象」
B
来说:B
不是一个「迭代器」,因为它只了实现__iter__
,没有实现__next__
由于 B
的__iter__
返回了A
的实例,而A
是一个迭代器,所以B
的实例b
是一个「可迭代对象」,换句话说,B
把迭代细节交给了A
B
这样,所以 B
的实例只能是「可迭代对象」,而不是「迭代器」。list
、tuple
、set
、dict
类型,都只是「可迭代对象」,但不是「迭代器」,因为它们都是把迭代细节交给了另外一个类,这个类才是真正的迭代器。>>> l = [1, 2]
# list 的迭代器是 list_iterator
>>> iter(l)
<list_iterator object at 0x1009c1c18>
# 执行的是 list_iterator 的 __next__
>>> iter(l).__next__()
>>> 1
# tuple 是可迭代对象
>>> t = ('a', 'b')
# tuple 的迭代器是 tuple_iterator
>>> iter(t)
<tuple_iterator object at 0x1009c1b00>
# 执行的是 tuple_iterator 的 __next__
>>> iter(t).__next__()
>>> a
# set 是可迭代对象
>>> s = {1, 2}
# set 的迭代器是 set_iterator
>>> iter(s)
<set_iterator object at 0x1009c70d8>
# 执行的是 set_iterator 的 __next__
>>> iter(s).__next__()
>>> 1
# dict 是可迭代对象
>>> d = {'a': 1, 'b': 2}
# dict 的迭代器是 dict_keyiterator
>>> iter(d)
# 执行的是 dict_keyiterator 的 __next__
<dict_keyiterator object at 0x1009c34f8>
>>> iter(d).next()
>>> a
list
类型为例,我们先定义 l = [1, 2]
,然后执行 iter(l)
得到 list
类型的迭代器是 list_iterator
,也就是说在迭代 list
时,其实执行的是 list_iterator
的 __next__
,list
把具体的迭代细节,交给了 list_iterator
。list
是一个可迭代对象,但它不是迭代器。其他类型 tuple
、set
、dict
也是同样的道理。生成器
生成器表达式 生成器函数
>>> g = (i for i in range(5))
>>> g
<generator object <genexpr> at 0x101334f50>
# 生成器就是一个迭代器
>>> iter(g)
<generator object <genexpr> at 0x101334f50>
# 生成器也是一个可迭代对象
>>> for i in g:
... print(i)
# 0 1 2 3 4
g = (i for i in range(5))
创建了一个生成器,它的类型是 generator
,同时调用 iter(g)
可以得知 __iter__
返回的是实例本身,即生成器也是一个迭代器,并且它也是一个可迭代对象。for i in range(n):
yield i
# 创建一个生成器
g = gen(5)
# <generator object gen at 0x10bb46f50>
print(g)
# <type 'generator'>
print(type(g))
# 迭代这个生成器
for i in g:
print(i)
# 0 1 2 3 4
yield
关键字。其实,包含 yield
关键字的函数,不再是一个普通的函数,而返回的是一个生成器。它在功能上与上面的例子一样,可以迭代生成器中的所有数据。yield
的方式来创建一个生成器。yield
的函数和使用 return
的普通函数,有什么区别了。yield
的函数与使用 return
的函数,在执行时的差别在于:包含 return
的方法会以return
关键字为最终返回,每次执行都返回相同的结果包含 yield
的方法一般用于迭代,每次执行时遇到yield
就返回yield
后的结果,但内部会保留上次执行的状态,下次继续迭代时,会继续执行yield
之后的代码,直到再次遇到yield
后返回
return
返回:# 创建一个集合
return [i for i in range(n)]
yield
生成器的方式迭代这个集合,就能解决内存占用大的问题:for i in range(n):
# 每次只返回一个元素
yield i
yield
时,才会返回一个元素,在这个过程中,不会一次性申请非常大的内存空间。当我们面对这种场景时,使用生成器就非常合适了。yield
时,再进行详细的分析。总结
__iter__
和 __next__
方法,那么它就是一个迭代器。如果只是实现了 __iter__
,并且这个方法返回的是一个迭代器类,那么这个类的实例就只是一个可迭代对象,因为它的迭代细节是交给了另一个类来处理。list
、tuple
、set
、dict
类型,它们并不是迭代器,只能叫做可迭代对象,它们的迭代细节都是交给了另一个类来处理的。由此我们也得知,一个迭代器一定是一个可迭代对象,但可迭代对象不一定是迭代器。yield
使用,我们可以实现懒惰计算的功能,同时,我们也可以用非常小的内存,来迭代一个大集合中的数据。